// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// Tests for the Command Buffer Helper.

#include "gpu/command_buffer/client/transfer_buffer.h"

#include <stddef.h>
#include <stdint.h>

#include <memory>

#include "base/compiler_specific.h"
#include "gpu/command_buffer/client/client_test_helper.h"
#include "gpu/command_buffer/client/cmd_buffer_helper.h"
#include "gpu/command_buffer/common/command_buffer.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

using ::testing::_;
using ::testing::AtMost;
using ::testing::Invoke;
using ::testing::Return;
using ::testing::SetArgPointee;
using ::testing::StrictMock;

namespace gpu {

class TransferBufferTest : public testing::Test {
protected:
    static const int32_t kNumCommandEntries = 400;
    static const int32_t kCommandBufferSizeBytes = kNumCommandEntries * sizeof(CommandBufferEntry);
    static const unsigned int kStartingOffset = 64;
    static const unsigned int kAlignment = 4;
    static const size_t kTransferBufferSize = 256;

    TransferBufferTest()
        : transfer_buffer_id_(0)
    {
    }

    void SetUp() override;
    void TearDown() override;

    virtual void Initialize(unsigned int size_to_flush)
    {
        ASSERT_TRUE(transfer_buffer_->Initialize(
            kTransferBufferSize,
            kStartingOffset,
            kTransferBufferSize,
            kTransferBufferSize,
            kAlignment,
            size_to_flush));
    }

    MockClientCommandBufferMockFlush* command_buffer() const
    {
        return command_buffer_.get();
    }

    std::unique_ptr<MockClientCommandBufferMockFlush> command_buffer_;
    std::unique_ptr<CommandBufferHelper> helper_;
    std::unique_ptr<TransferBuffer> transfer_buffer_;
    int32_t transfer_buffer_id_;
};

void TransferBufferTest::SetUp()
{
    command_buffer_.reset(new StrictMock<MockClientCommandBufferMockFlush>());

    helper_.reset(new CommandBufferHelper(command_buffer()));
    ASSERT_TRUE(helper_->Initialize(kCommandBufferSizeBytes));

    transfer_buffer_id_ = command_buffer()->GetNextFreeTransferBufferId();

    transfer_buffer_.reset(new TransferBuffer(helper_.get()));
}

void TransferBufferTest::TearDown()
{
    if (transfer_buffer_->HaveBuffer()) {
        EXPECT_CALL(*command_buffer(), DestroyTransferBuffer(_))
            .Times(1)
            .RetiresOnSaturation();
    }
    // For command buffer.
    EXPECT_CALL(*command_buffer(), DestroyTransferBuffer(_))
        .Times(1)
        .RetiresOnSaturation();
    EXPECT_CALL(*command_buffer(), OnFlush()).Times(AtMost(1));
    EXPECT_CALL(*command_buffer(), Flush(_)).Times(AtMost(1));
    transfer_buffer_.reset();
}

// GCC requires these declarations, but MSVC requires they not be present
#ifndef _MSC_VER
const int32_t TransferBufferTest::kNumCommandEntries;
const int32_t TransferBufferTest::kCommandBufferSizeBytes;
const unsigned int TransferBufferTest::kStartingOffset;
const unsigned int TransferBufferTest::kAlignment;
const size_t TransferBufferTest::kTransferBufferSize;
#endif

TEST_F(TransferBufferTest, Basic)
{
    Initialize(0);
    EXPECT_TRUE(transfer_buffer_->HaveBuffer());
    EXPECT_EQ(transfer_buffer_id_, transfer_buffer_->GetShmId());
    EXPECT_EQ(
        kTransferBufferSize - kStartingOffset,
        transfer_buffer_->GetCurrentMaxAllocationWithoutRealloc());
}

TEST_F(TransferBufferTest, Free)
{
    Initialize(0);
    EXPECT_TRUE(transfer_buffer_->HaveBuffer());
    EXPECT_EQ(transfer_buffer_id_, transfer_buffer_->GetShmId());

    // Free buffer.
    EXPECT_CALL(*command_buffer(), DestroyTransferBuffer(_))
        .Times(1)
        .RetiresOnSaturation();
    transfer_buffer_->Free();
    // See it's freed.
    EXPECT_FALSE(transfer_buffer_->HaveBuffer());
    // See that it gets reallocated.
    EXPECT_EQ(transfer_buffer_id_, transfer_buffer_->GetShmId());
    EXPECT_TRUE(transfer_buffer_->HaveBuffer());

    // Free buffer.
    EXPECT_CALL(*command_buffer(), DestroyTransferBuffer(_))
        .Times(1)
        .RetiresOnSaturation();
    transfer_buffer_->Free();
    // See it's freed.
    EXPECT_FALSE(transfer_buffer_->HaveBuffer());
    // See that it gets reallocated.
    EXPECT_TRUE(transfer_buffer_->GetResultBuffer() != NULL);
    EXPECT_TRUE(transfer_buffer_->HaveBuffer());

    // Free buffer.
    EXPECT_CALL(*command_buffer(), DestroyTransferBuffer(_))
        .Times(1)
        .RetiresOnSaturation();
    transfer_buffer_->Free();
    // See it's freed.
    EXPECT_FALSE(transfer_buffer_->HaveBuffer());
    // See that it gets reallocated.
    unsigned int size = 0;
    void* data = transfer_buffer_->AllocUpTo(1, &size);
    EXPECT_TRUE(data != NULL);
    EXPECT_TRUE(transfer_buffer_->HaveBuffer());
    transfer_buffer_->FreePendingToken(data, 1);

    // Free buffer.
    EXPECT_CALL(*command_buffer(), DestroyTransferBuffer(_))
        .Times(1)
        .RetiresOnSaturation();
    transfer_buffer_->Free();
    // See it's freed.
    EXPECT_FALSE(transfer_buffer_->HaveBuffer());
    // See that it gets reallocated.
    transfer_buffer_->GetResultOffset();
    EXPECT_TRUE(transfer_buffer_->HaveBuffer());

    EXPECT_EQ(
        kTransferBufferSize - kStartingOffset,
        transfer_buffer_->GetCurrentMaxAllocationWithoutRealloc());

    // Test freeing twice.
    EXPECT_CALL(*command_buffer(), DestroyTransferBuffer(_))
        .Times(1)
        .RetiresOnSaturation();
    transfer_buffer_->Free();
    transfer_buffer_->Free();
}

TEST_F(TransferBufferTest, TooLargeAllocation)
{
    Initialize(0);
    // Check that we can't allocate large than max size.
    void* ptr = transfer_buffer_->Alloc(kTransferBufferSize + 1);
    EXPECT_TRUE(ptr == NULL);
    // Check we if we try to allocate larger than max we get max.
    unsigned int size_allocated = 0;
    ptr = transfer_buffer_->AllocUpTo(
        kTransferBufferSize + 1, &size_allocated);
    ASSERT_TRUE(ptr != NULL);
    EXPECT_EQ(kTransferBufferSize - kStartingOffset, size_allocated);
    transfer_buffer_->FreePendingToken(ptr, 1);
}

TEST_F(TransferBufferTest, MemoryAlignmentAfterZeroAllocation)
{
    Initialize(32u);
    void* ptr = transfer_buffer_->Alloc(0);
    EXPECT_EQ((reinterpret_cast<uintptr_t>(ptr) & (kAlignment - 1)), 0u);
    transfer_buffer_->FreePendingToken(ptr, static_cast<unsigned int>(-1));
    // Check that the pointer is aligned on the following allocation.
    ptr = transfer_buffer_->Alloc(4);
    EXPECT_EQ((reinterpret_cast<uintptr_t>(ptr) & (kAlignment - 1)), 0u);
    transfer_buffer_->FreePendingToken(ptr, 1);
}

TEST_F(TransferBufferTest, Flush)
{
    Initialize(16u);
    unsigned int size_allocated = 0;
    for (int i = 0; i < 8; ++i) {
        void* ptr = transfer_buffer_->AllocUpTo(8u, &size_allocated);
        ASSERT_TRUE(ptr != NULL);
        EXPECT_EQ(8u, size_allocated);
        if (i % 2) {
            EXPECT_CALL(*command_buffer(), Flush(_))
                .Times(1)
                .RetiresOnSaturation();
        }
        transfer_buffer_->FreePendingToken(ptr, helper_->InsertToken());
    }
    for (int i = 0; i < 8; ++i) {
        void* ptr = transfer_buffer_->Alloc(8u);
        ASSERT_TRUE(ptr != NULL);
        if (i % 2) {
            EXPECT_CALL(*command_buffer(), Flush(_))
                .Times(1)
                .RetiresOnSaturation();
        }
        transfer_buffer_->FreePendingToken(ptr, helper_->InsertToken());
    }
}

class MockClientCommandBufferCanFail : public MockClientCommandBufferMockFlush {
public:
    MockClientCommandBufferCanFail()
    {
    }
    virtual ~MockClientCommandBufferCanFail()
    {
    }

    MOCK_METHOD2(CreateTransferBuffer,
        scoped_refptr<Buffer>(size_t size, int32_t* id));

    scoped_refptr<gpu::Buffer> RealCreateTransferBuffer(size_t size,
        int32_t* id)
    {
        return MockCommandBufferBase::CreateTransferBuffer(size, id);
    }
};

class TransferBufferExpandContractTest : public testing::Test {
protected:
    static const int32_t kNumCommandEntries = 400;
    static const int32_t kCommandBufferSizeBytes = kNumCommandEntries * sizeof(CommandBufferEntry);
    static const unsigned int kStartingOffset = 64;
    static const unsigned int kAlignment = 4;
    static const size_t kStartTransferBufferSize = 256;
    static const size_t kMaxTransferBufferSize = 1024;
    static const size_t kMinTransferBufferSize = 128;

    TransferBufferExpandContractTest()
        : transfer_buffer_id_(0)
    {
    }

    void SetUp() override;
    void TearDown() override;

    MockClientCommandBufferCanFail* command_buffer() const
    {
        return command_buffer_.get();
    }

    std::unique_ptr<MockClientCommandBufferCanFail> command_buffer_;
    std::unique_ptr<CommandBufferHelper> helper_;
    std::unique_ptr<TransferBuffer> transfer_buffer_;
    int32_t transfer_buffer_id_;
};

void TransferBufferExpandContractTest::SetUp()
{
    command_buffer_.reset(new StrictMock<MockClientCommandBufferCanFail>());

    EXPECT_CALL(*command_buffer(),
        CreateTransferBuffer(kCommandBufferSizeBytes, _))
        .WillOnce(Invoke(
            command_buffer(),
            &MockClientCommandBufferCanFail::RealCreateTransferBuffer))
        .RetiresOnSaturation();

    helper_.reset(new CommandBufferHelper(command_buffer()));
    ASSERT_TRUE(helper_->Initialize(kCommandBufferSizeBytes));

    transfer_buffer_id_ = command_buffer()->GetNextFreeTransferBufferId();

    EXPECT_CALL(*command_buffer(),
        CreateTransferBuffer(kStartTransferBufferSize, _))
        .WillOnce(Invoke(
            command_buffer(),
            &MockClientCommandBufferCanFail::RealCreateTransferBuffer))
        .RetiresOnSaturation();

    transfer_buffer_.reset(new TransferBuffer(helper_.get()));
    ASSERT_TRUE(transfer_buffer_->Initialize(
        kStartTransferBufferSize,
        kStartingOffset,
        kMinTransferBufferSize,
        kMaxTransferBufferSize,
        kAlignment,
        0));
}

void TransferBufferExpandContractTest::TearDown()
{
    if (transfer_buffer_->HaveBuffer()) {
        EXPECT_CALL(*command_buffer(), DestroyTransferBuffer(_))
            .Times(1)
            .RetiresOnSaturation();
    }
    // For command buffer.
    EXPECT_CALL(*command_buffer(), DestroyTransferBuffer(_))
        .Times(1)
        .RetiresOnSaturation();
    transfer_buffer_.reset();
}

// GCC requires these declarations, but MSVC requires they not be present
#ifndef _MSC_VER
const int32_t TransferBufferExpandContractTest::kNumCommandEntries;
const int32_t TransferBufferExpandContractTest::kCommandBufferSizeBytes;
const unsigned int TransferBufferExpandContractTest::kStartingOffset;
const unsigned int TransferBufferExpandContractTest::kAlignment;
const size_t TransferBufferExpandContractTest::kStartTransferBufferSize;
const size_t TransferBufferExpandContractTest::kMaxTransferBufferSize;
const size_t TransferBufferExpandContractTest::kMinTransferBufferSize;
#endif

TEST_F(TransferBufferExpandContractTest, Expand)
{
    // Check it starts at starting size.
    EXPECT_EQ(
        kStartTransferBufferSize - kStartingOffset,
        transfer_buffer_->GetCurrentMaxAllocationWithoutRealloc());

    EXPECT_CALL(*command_buffer(), DestroyTransferBuffer(_))
        .Times(1)
        .RetiresOnSaturation();
    EXPECT_CALL(*command_buffer(),
        CreateTransferBuffer(kStartTransferBufferSize * 2, _))
        .WillOnce(Invoke(
            command_buffer(),
            &MockClientCommandBufferCanFail::RealCreateTransferBuffer))
        .RetiresOnSaturation();

    // Try next power of 2.
    const size_t kSize1 = 512 - kStartingOffset;
    unsigned int size_allocated = 0;
    void* ptr = transfer_buffer_->AllocUpTo(kSize1, &size_allocated);
    ASSERT_TRUE(ptr != NULL);
    EXPECT_EQ(kSize1, size_allocated);
    EXPECT_EQ(kSize1, transfer_buffer_->GetCurrentMaxAllocationWithoutRealloc());
    transfer_buffer_->FreePendingToken(ptr, 1);

    EXPECT_CALL(*command_buffer(), DestroyTransferBuffer(_))
        .Times(1)
        .RetiresOnSaturation();
    EXPECT_CALL(*command_buffer(),
        CreateTransferBuffer(kMaxTransferBufferSize, _))
        .WillOnce(Invoke(
            command_buffer(),
            &MockClientCommandBufferCanFail::RealCreateTransferBuffer))
        .RetiresOnSaturation();

    // Try next power of 2.
    const size_t kSize2 = 1024 - kStartingOffset;
    ptr = transfer_buffer_->AllocUpTo(kSize2, &size_allocated);
    ASSERT_TRUE(ptr != NULL);
    EXPECT_EQ(kSize2, size_allocated);
    EXPECT_EQ(kSize2, transfer_buffer_->GetCurrentMaxAllocationWithoutRealloc());
    transfer_buffer_->FreePendingToken(ptr, 1);

    // Try next one more. Should not go past max.
    size_allocated = 0;
    const size_t kSize3 = kSize2 + 1;
    ptr = transfer_buffer_->AllocUpTo(kSize3, &size_allocated);
    EXPECT_EQ(kSize2, size_allocated);
    EXPECT_EQ(kSize2, transfer_buffer_->GetCurrentMaxAllocationWithoutRealloc());
    transfer_buffer_->FreePendingToken(ptr, 1);
}

TEST_F(TransferBufferExpandContractTest, Contract)
{
    // Check it starts at starting size.
    EXPECT_EQ(
        kStartTransferBufferSize - kStartingOffset,
        transfer_buffer_->GetCurrentMaxAllocationWithoutRealloc());

    // Free buffer.
    EXPECT_CALL(*command_buffer(), DestroyTransferBuffer(_))
        .Times(1)
        .RetiresOnSaturation();
    transfer_buffer_->Free();
    // See it's freed.
    EXPECT_FALSE(transfer_buffer_->HaveBuffer());

    // Try to allocate again, fail first request
    EXPECT_CALL(*command_buffer(),
        CreateTransferBuffer(kStartTransferBufferSize, _))
        .WillOnce(
            DoAll(SetArgPointee<1>(-1), Return(scoped_refptr<gpu::Buffer>())))
        .RetiresOnSaturation();
    EXPECT_CALL(*command_buffer(),
        CreateTransferBuffer(kMinTransferBufferSize, _))
        .WillOnce(Invoke(
            command_buffer(),
            &MockClientCommandBufferCanFail::RealCreateTransferBuffer))
        .RetiresOnSaturation();

    const size_t kSize1 = 256 - kStartingOffset;
    const size_t kSize2 = 128 - kStartingOffset;
    unsigned int size_allocated = 0;
    void* ptr = transfer_buffer_->AllocUpTo(kSize1, &size_allocated);
    ASSERT_TRUE(ptr != NULL);
    EXPECT_EQ(kSize2, size_allocated);
    EXPECT_EQ(kSize2, transfer_buffer_->GetCurrentMaxAllocationWithoutRealloc());
    transfer_buffer_->FreePendingToken(ptr, 1);

    // Free buffer.
    EXPECT_CALL(*command_buffer(), DestroyTransferBuffer(_))
        .Times(1)
        .RetiresOnSaturation();
    transfer_buffer_->Free();
    // See it's freed.
    EXPECT_FALSE(transfer_buffer_->HaveBuffer());

    // Try to allocate again,
    EXPECT_CALL(*command_buffer(),
        CreateTransferBuffer(kMinTransferBufferSize, _))
        .WillOnce(Invoke(
            command_buffer(),
            &MockClientCommandBufferCanFail::RealCreateTransferBuffer))
        .RetiresOnSaturation();

    ptr = transfer_buffer_->AllocUpTo(kSize1, &size_allocated);
    ASSERT_TRUE(ptr != NULL);
    EXPECT_EQ(kSize2, size_allocated);
    EXPECT_EQ(kSize2, transfer_buffer_->GetCurrentMaxAllocationWithoutRealloc());
    transfer_buffer_->FreePendingToken(ptr, 1);
}

TEST_F(TransferBufferExpandContractTest, OutOfMemory)
{
    // Free buffer.
    EXPECT_CALL(*command_buffer(), DestroyTransferBuffer(_))
        .Times(1)
        .RetiresOnSaturation();
    transfer_buffer_->Free();
    // See it's freed.
    EXPECT_FALSE(transfer_buffer_->HaveBuffer());

    // Try to allocate again, fail both requests.
    EXPECT_CALL(*command_buffer(), CreateTransferBuffer(_, _))
        .WillOnce(
            DoAll(SetArgPointee<1>(-1), Return(scoped_refptr<gpu::Buffer>())))
        .WillOnce(
            DoAll(SetArgPointee<1>(-1), Return(scoped_refptr<gpu::Buffer>())))
        .WillOnce(
            DoAll(SetArgPointee<1>(-1), Return(scoped_refptr<gpu::Buffer>())))
        .RetiresOnSaturation();

    const size_t kSize1 = 512 - kStartingOffset;
    unsigned int size_allocated = 0;
    void* ptr = transfer_buffer_->AllocUpTo(kSize1, &size_allocated);
    ASSERT_TRUE(ptr == NULL);
    EXPECT_FALSE(transfer_buffer_->HaveBuffer());
}

TEST_F(TransferBufferExpandContractTest, ReallocsToDefault)
{
    // Free buffer.
    EXPECT_CALL(*command_buffer(), DestroyTransferBuffer(_))
        .Times(1)
        .RetiresOnSaturation();
    transfer_buffer_->Free();
    // See it's freed.
    EXPECT_FALSE(transfer_buffer_->HaveBuffer());

    // See that it gets reallocated.
    EXPECT_CALL(*command_buffer(),
        CreateTransferBuffer(kStartTransferBufferSize, _))
        .WillOnce(Invoke(
            command_buffer(),
            &MockClientCommandBufferCanFail::RealCreateTransferBuffer))
        .RetiresOnSaturation();
    EXPECT_EQ(transfer_buffer_id_, transfer_buffer_->GetShmId());
    EXPECT_TRUE(transfer_buffer_->HaveBuffer());

    // Check it's the default size.
    EXPECT_EQ(
        kStartTransferBufferSize - kStartingOffset,
        transfer_buffer_->GetCurrentMaxAllocationWithoutRealloc());
}

} // namespace gpu
